/*      $Id: mod_log_apache.c 1119 2010-12-12 21:29:18Z phrakt $    */
/*
 * Copyright (c) 2007 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>

#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <canopy/log.h>
#include <canopy/sys.h>
#include <canopy/string.h>

#include <http/http.h>
#include <http/msg.h>
#include <http/addr.h>
#include <http/sock.h>
#include <http/entity.h>

#include <httpd/httpd.h>



static const char months[][4] = {
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
};


static char  access_log_path[CANOPY_PATH_MAX] = "/tmp/access_log";
static char  error_log_path[CANOPY_PATH_MAX] = "/tmp/error_log";

static char  access_log_format[64] = "%h %l %u %t \"%r\" %>s %b";



static int   error_log_fd = -1;
static int   access_log_fd = -1;


int  mod_log_apache_init        (void);
void mod_log_apache_cleanup     (void);
int  mod_log_apache_access_hdlr (struct httpd_hdlr_args *);


static void mod_log_apache_error_hdlr  (struct cnp_log_msg *, void *);


/*
 * mod_log_apache_init()
 *
 */
int
mod_log_apache_init(void)
{
	int flags = O_WRONLY | O_CREAT | O_APPEND;
	cnp_logmask_t mask;

	cnp_log(httpd_logchan, CNP_LOG_DEBUG, "opening access log file %s",
	    access_log_path);

	access_log_fd = open(access_log_path, flags, 0400);
	if (access_log_fd == -1) {
		cnp_log(httpd_logchan, CNP_LOG_ERRNO,
		    "failed to open access log file %s", access_log_path);
		return (-1);
	}

	cnp_log(httpd_logchan, CNP_LOG_DEBUG, "opening error log file %s",
	    error_log_path);

	error_log_fd = open(error_log_path, flags, 0400);
	if (error_log_fd == -1) {
		cnp_log(httpd_logchan, CNP_LOG_ERRNO,
		    "failed to open error log file %s", error_log_path);
		return (-1);
	}

	if (canopy_addhdlr(HTTPD_PHASE_POSTRESP, HTTPD_HDLR_HEAD,
	    "mod_log_apache_handler", mod_log_apache_access_hdlr, NULL) == -1) {
		return (-1);
	}

	CNP_LOG_CLRMASK(mask);
	CNP_LOG_MASKPRI(mask, CNP_LOG_TRACE);
	CNP_LOG_MASKPRI(mask, CNP_LOG_DEBUG);
	CNP_LOG_MASKPRI(mask, CNP_LOG_INFO);

	cnp_log_addhdlr(httpd_logchan, mask, mod_log_apache_error_hdlr, NULL);

	return (0);
}

/*
 * mod_log_apache_cleanup()
 *
 */
void
mod_log_apache_cleanup(void)
{

	if (close(access_log_fd) == -1) {
		cnp_log(httpd_logchan, CNP_LOG_ERRNO,
		    "failed to close access log file %s", access_log_path);
	}

	if (close(error_log_fd) == -1) {
		cnp_log(httpd_logchan, CNP_LOG_ERRNO,
		    "failed to close error log file %s", error_log_path);
	}
}

/*
 *
 */
int
mod_log_apache_access_hdlr(struct httpd_hdlr_args *args)
{
	size_t ret;
	long gmthroff;
	uint64_t size = 0;
	char sign, buf[64], *fp;
	cnp_buf_t *linebuf;
	const char *sp;
	struct tm tm_data;
	http_sockaddr_t addr;
	http_msg_t *req = args->ha_req;
	http_msg_t *resp = args->ha_resp;

	linebuf = cnp_buf_alloc(256, CNP_BUF_AUTOEXT);
	if (linebuf == NULL) {
	}

	for (fp = access_log_format; *fp != '\0'; fp++) {
		buf[0] = '\0';
		if (*fp == '%') {
			fp++;

			switch (*fp) {
			case 'B':
			case 'b':
				http_entity_getsize(resp->msg_entity, &size);
				cnp_buf_fappend(linebuf, "%u", size);
				break;

			case 'h':
				http_sock_getraddr(args->ha_sock, &addr);
				sp = http_addr_ntop(&addr, buf, sizeof(buf));
				if (sp != NULL)
					cnp_buf_fappend(linebuf, "%s", sp);
				break;

			case 'm':
				cnp_buf_fappend(linebuf, "%s",
				    req->msg_req.req_methodname);
				break;

			case 'r':
				if (req->msg_version == HTTP_VERSION_09)
					strlcpy(buf, "0.9", sizeof(buf));
				else if (req->msg_version == HTTP_VERSION_10)
					strlcpy(buf, "1.0", sizeof(buf));
				else if (req->msg_version == HTTP_VERSION_11)
					strlcpy(buf, "1.1", sizeof(buf));

				cnp_buf_fappend(linebuf, "%s %s HTTP/%s",
				    req->msg_req.req_methodname,
				    http_uri_getpath(req->msg_req.req_url),
				    buf);
				break;

			case 't':
				localtime_r(&req->msg_crtime, &tm_data);

				//sign = (tm_data.tm_gmtoff > 0) ? '+' : '-';
				cnp_buf_fappend(linebuf,
				    "[%2d/%s/%4d:%02d:%02d:%02d %+05ld]",
				    tm_data.tm_mday, months[tm_data.tm_mon],
				    tm_data.tm_year + 1900,
				    tm_data.tm_hour, tm_data.tm_min,
				    tm_data.tm_sec,
				    tm_data.tm_gmtoff / 36);

				break;
			case 'u':
				cnp_buf_putc(linebuf, '-');
				break;

			case '>':
				fp++;
				if (*fp == 's')
					cnp_buf_fappend(linebuf, "%d",
					    resp->msg_resp.resp_code);
				break;

			case '%':
				cnp_buf_putc(linebuf, '%');
				break;

			default:
				cnp_buf_putc(linebuf, '-');
				break;
			}
		}
		else {
			cnp_buf_putc(linebuf, *fp);
		}
	}

	cnp_buf_putc(linebuf, '\n');

	if (cnp_buf_writefd(linebuf, access_log_fd) == -1) {
		cnp_log(httpd_logchan, CNP_LOG_ERRNO,
		    "failed to write to access log file %s", access_log_path);
	}

	cnp_buf_free(linebuf);

	return (HTTPD_HDLR_OK);
}

/*
 * mod_log_apache_error_hdlr()
 *
 *
 */
static void
mod_log_apache_error_hdlr(struct cnp_log_msg *msg, void *arg)
{
	char buf[1024], datebuf[32];
	char *sp, *level;

	if (ctime_r((time_t *)&msg->msg_time.tv_sec, datebuf) == NULL)
		return;

	if ((sp = strrchr(datebuf, '\n')) != NULL)
		*sp = '\0';

	snprintf(buf, sizeof(buf), "[%s] [%s] %s\n",
	    datebuf, cnp_log_pri_names[msg->msg_priority], msg->msg_string);

	(void)write(error_log_fd, buf, strlen(buf));
}
